;;; a wrap view that wraps its subviews similar to text-area

(in-package :gwee)

(defclass wrap-layout (view background-mixin margin-mixin cell-mixin)
  ())

(defun place-views (view width height dim-fn gravitate)
  "The wrap logic. Returns the bottom of the last line placed. This is
used for calculating the minimum height."
  (macrolet ((forward ()
               `(let ((w (+ vw (cell-padding view))))
                  (push v row-views)
                  (incf x w)
                  (decf remaining-width w)
                  (setf row-height (max row-height vh))))
             (gravitate-row ()
               `(when gravitate
                  (loop for i in row-views
                     for g = (if (listp (cell-gravity view))
                                 (cell-gravity view)
                                 (list (cell-gravity view)))
                     then (or (cdr g) g) do
                     (ecase (car g)
                       (:top
                        (setf (view-y i) y))
                       (:center
                        (setf (view-y i) (+ y (u:half (- row-height (view-height i))))))
                       (:bottom
                        (setf (view-y i) (- (+ y row-height) (view-height i))))))))
             (next-line (&body body)
               `(let ((h (+ row-height (cell-padding view))))
                  (gravitate-row)
                  (incf y h)
                  (decf remaining-height h)
                  (setf remaining-height (max 0 remaining-height))
                  ,@body))
             (reset-x ()
               `(setf x (margin-left view)
                      remaining-width max-width
                      row-views nil
                      row-height 0)))
    (loop
       for v in (view-subviews view)
       with row-views = nil
       with max-width = (- width (margin-left view) (margin-right view))
       with max-height = (- height (margin-top view) (margin-bottom view))
       with x = (margin-left view)
       with y = (margin-top view)
       with remaining-width = max-width
       with remaining-height = max-height
       with row-height = 0
       do
         (multiple-value-bind (vw vh) (funcall dim-fn v view x y remaining-width remaining-height)
           (cond
             ((> vw remaining-width)
              (next-line
               (reset-x)
               (multiple-value-bind (vw vh) (funcall dim-fn v view x y remaining-width remaining-height)
                 (forward)
                 (when (>= vw max-width)
                   (next-line)
                   (reset-x)))))
             ((= vw remaining-width)
              (forward)
              (next-line
               (reset-x)))
             (t
              (forward))))
         finally (gravitate-row) (return (+ y row-height)))))

;; FIXME: if a subview is set to maximize its height then it'll take
;; up the rest of the height leaving no more room for other
;; views. Then other height maximizing views will be invisible with a
;; height of 0. How do we properly deal with the subviews taking up
;; more space than we have?
(defmethod synchronize-subview-dimensions ((view wrap-layout))
  (place-views view (view-width view) (view-height view)
               (lambda (v view x y remaining-width remaining-height)
                 (synchronize-dimensions v view x y remaining-width remaining-height)
                 (values (view-width v) (view-height v)))
               t)
  (dolist (i (view-subviews view))
    (synchronize-subview-dimensions i)))

(defmethod synchronize-width-minimum ((view wrap-layout) (parent view) width height)
  ;; since synchronize-width is expensive, call it as little as
  ;; possible.
  (loop for i in (view-subviews view)
     sum (synchronize-width i view width height) into sum
     when (> sum width)
     return width
     finally (return sum)))

(defmethod synchronize-height-minimum ((view wrap-layout) (parent view) width height)
  "There is no shortcut to figuring out the minimum height. We simply
have to run through the logic to find the answer."
  (let* ((w (synchronize-width view parent width height))
         (h (place-views view w height
                        (lambda (v view x y remaining-width remaining-height)
                          (declare (ignore x y))
                          (values (synchronize-width v view remaining-width remaining-height)
                                  (synchronize-height v view remaining-width remaining-height)))
                        nil)))
    (min height (+ h (margin-bottom view)))))
    
(defun testit ()
  (with-gui (1024 768 :font "sans.ttf":background (gwee:gray 255) :foreground (gwee:gray 0))
    (let ((w (make-instance 'wrap-layout :margin 10 :padding 0 :height :minimum  :width 300 :parent *top-view* :border-color (gwee:gray 0) :background (gwee:gray 255))))
      (make-instance 'text-area :data "hey man poop" :parent w :background (gwee:color (random 255) (random 255) (random 255)))
      (make-instance 'text-area :data "Munch" :width :maximum :parent w :background (gwee:color (random 255) (random 255) (random 255)) :font (gwee:open-font "sans.ttf" 48))
      (make-instance 'text-area :data "dink" :width :maximum :parent w :background (gwee:color (random 255) (random 255) (random 255)))
      (make-instance 'button :data "Lazer fission" :parent w)
      (make-instance 'text-area :data "fizz" :parent w :background (gwee:color (random 255) (random 255) (random 255)))
      (make-instance 'text-area :data "bang bang dork dink" :parent w :background (gwee:color (random 255) (random 255) (random 255)))
      (make-instance 'text-area :data "dink" :parent w :background (gwee:color (random 255) (random 255) (random 255)))
      (make-instance 'text-area :data "d" :parent w :background (gwee:color (random 255) (random 255) (random 255)) :font (gwee:open-font "sans.ttf" 48))
;;       (make-instance 'text-area :data "dman" :parent w :background (gwee:color (random 255) (random 255) (random 255)))
      )))